﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization;
using System.Xml.Serialization;
using System.Xml;

namespace Paxos
{
    [DataContract]
    public class AcceptorStatus
    {
        [DataMember]
        public SelfAppointProposal LastPd;

        [DataMember]
        public ConcreteProposal LastAd;
    }
    
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Single)]
    class Acceptor : IPaxosAcceptor
    {
        internal SelfAppointProposal _lastPromised;
        internal ConcreteProposal _lastAccepted;
        private ReplicaStateMachine _replica;
        private string _configFileName;

        public Acceptor(string configFileName, ReplicaStateMachine replica)
        {
            if (String.IsNullOrEmpty(configFileName))
            {
                throw new ArgumentException("ConfigFileName cannot be null or empty");
            }                
            _replica = replica;
            _configFileName = configFileName;
            // recovery from file
            Load();
        }

        public PromiseResult Promise(SelfAppointProposal proposal)
        {
            // need to turn into new round
            if (proposal.Ballot > _lastPromised.Ballot)
            {
                // sync
                CatchUp();
                if (proposal.Ballot > _lastPromised.Ballot)
                {
                    Console.WriteLine("Failed to sync to latest commands");
                    return new PromiseResult 
                    { 
                        Answer = false, 
                        Promised= _replica._localAcceptor._lastPromised,
                        Accepted = null 
                    };
                }
            }
            
            // proposal < _lastPromised
            if (proposal.CompareTo(_lastPromised) == -1)
            {
                return new PromiseResult() 
                { 
                    Answer = false, 
                    Promised = _lastPromised,
                    Accepted = null 
                };
            }
            else
            {
                // log
                Save(new AcceptorStatus { LastPd = proposal, LastAd = _lastAccepted });

                _lastPromised = proposal;
                return new PromiseResult() 
                { 
                    Answer = true,
                    Promised = _lastPromised,
                    Accepted = _lastAccepted
                };
            }
        }

        public AcceptResult Accept(ConcreteProposal proposal)
        {
            if (proposal.Ballot > _lastPromised.Ballot)
            {
                CatchUp();
                if (proposal.Ballot > _lastPromised.Ballot)
                {
                    Console.WriteLine("Failed to sync to latest commands");
                    return new AcceptResult() 
                    { 
                        Answer = false, 
                        Promised = _lastPromised
                    };
                }
            }

            if (proposal.CompareTo(_lastPromised) == -1)
            {
                return new AcceptResult() 
                { 
                    Answer = false, 
                    Promised = _lastPromised
                };
            }
            else
            {
                // log
                Save(new AcceptorStatus { LastPd = _lastPromised, LastAd = proposal });

                _lastAccepted = proposal;
                return new AcceptResult() 
                { 
                    Answer = true, 
                    Promised = _lastPromised
                }; 
            }
        }

        // Act as trivial learner
        public void Commit(ConcreteProposal proposal)
        {
            if (proposal.Ballot < _lastPromised.Ballot)
            {
                throw new Exception("Cannot commit old proposal");
            }

            // Log status
            Save(new AcceptorStatus { 
                LastPd = new SelfAppointProposal() { 
                    Ballot = _lastPromised.Ballot + 1, 
                    ID = 0,
                    DecreeGuid=_replica._localAcceptor._lastPromised.DecreeGuid },
                LastAd = null });

            // start new paxos
            _lastPromised.Ballot++;
            _lastPromised.ID = 0;
            _lastAccepted = null;
            
            // log command
            _replica.LogCommand(proposal.Command);

            // commit command
            proposal.Command.Commit(_replica);
            Console.WriteLine("Commit {0}", proposal);
            Console.WriteLine("Current Status Pd {0} Ad {1}", _lastPromised, _lastAccepted);
        }

        internal void Save(AcceptorStatus s)
        {
            DataContractSerializer dcs = new DataContractSerializer(typeof(AcceptorStatus));
            using (FileStream f = new FileStream(_configFileName, FileMode.Create, FileAccess.Write))
            {
                dcs.WriteObject(f, s);
            }
        }

        internal void Load()
        {
            if (File.Exists(_configFileName))
            {
                try
                {
                    DataContractSerializer dcs = new DataContractSerializer(typeof(AcceptorStatus));
                    using (FileStream f = new FileStream(_configFileName, FileMode.Open))
                    {
                        AcceptorStatus s = dcs.ReadObject(f) as AcceptorStatus;
                        _lastPromised = s.LastPd;
                        _lastAccepted = s.LastAd;
                    }
                }
                catch
                {
                    Console.WriteLine("Config file corrupted!");
                }
            }
            else
            {
                _lastPromised = new SelfAppointProposal()
                {
                    Ballot = 0,
                    ID = 0,
                    DecreeGuid = _replica._id
                };
                
                Save(
                    new AcceptorStatus()
                    {
                        LastPd = _lastPromised,
                        LastAd = null
                    }
                );

            }
        }

        public void CatchUp()
        {
            Console.WriteLine("Catch-Up");
            foreach (IPaxosProproser p in _replica._proposers)
            {
                try
                {
                    PaxosCommand[] cmds = p.GetLatest(_lastPromised.Ballot);
                    foreach (PaxosCommand cmd in cmds)
                    {
                        _replica._commandLogger.Log(cmd);
                        cmd.Commit(_replica);

                        Save(
                            new AcceptorStatus()
                            {
                                LastPd = new SelfAppointProposal()
                                {
                                    Ballot = _lastPromised.Ballot + 1,
                                    ID = 0,
                                    DecreeGuid = _replica._id
                                }
                                ,
                                LastAd = null
                            }
                        );

                        _lastPromised.Ballot++;
                        _lastPromised.ID = 0;
                        _lastPromised.DecreeGuid = _replica._id;
                        _lastAccepted = null;
                    }
                }
                catch
                {
                }
            }
        }
    }
}
